#include <iostream>
#include "Common.hpp"
#include "Socket.hpp"
#include <sys/poll.h>

using namespace myLog;
using namespace mySocket;

class PollServer{
public:
    const static int default_size = 2;
    const static int defaultfd = -1;
private:
    void Init_pfds(int size, struct pollfd* fds){
        for(int i = 0; i < size; ++i) {
            fds[i].fd = defaultfd;
            fds[i].events = 0;
            fds[i].revents = 0;
        }
    }

public:
    PollServer(uint16_t port, size_t pfds_size = default_size)
        :_port(port),
        _is_running(false),
        _listensock(std::make_unique<TcpSocket>()),
        _pfds_size(pfds_size)
    {
        _listensock->CreateServerTcpSocket(_port);
        // 默认开辟1024个大小的数组 -> 不够就扩容
        _pfds = new struct pollfd[_pfds_size];
        Init_pfds(_pfds_size, _pfds);

        // 把监听套接字带入
        _pfds[0].fd = _listensock->GetFd();
        _pfds[0].events |= POLLIN;
    }
    ~PollServer(){}

    void Start(){
        _is_running = true;
        while(_is_running){
            // 1.因为这里是使用poll的多路转接技术，输入参数和输出参数是分开的！
            // 👉不需要把辅助数组导入到集合内！ //也没有所谓的集合，事件都是在struct pollfd内设置的！
            
            //Debug 测试 打印辅助数组的fd
            PrintFdArray();

            // poll接口最后一个参数是超时时间，单位ms
            //如果设置为-1，阻塞；如果设置为0，非阻塞轮询！
            int timeout = 1000 /*0*/ /*-1*/;
            int n = poll(_pfds, _pfds_size, -1);

            //poll也有三种返回值，类似于select
            // <0 出错； =0 等待超时  >0说明有文件描述符对应的事件就绪了，返回的值是就绪文件描述符的个数！
            if(n < 0){
                LOG(LogLevel::ERROR) << "poll error!";
                exit(SELECT_ERROR);
            }
            else if(n == 0){
                LOG(LogLevel::INFO) << "poll timeout!";
                continue;
            }
            // 当 n > 0 的时候，说明此时已就绪的文件描述符就已经被设置到了rfds下！
            // 但因为后面获取链接后，新的文件描述符也要被select接
            // 所以这里其实是不知道就绪的文件描述符是listensockfd，还是sockfd！！！
            else{
                // 但是没有关系，我们这里不进行处理，递交给 <事件派发器> 处理
                Dispatcher();
            }
        }
        _is_running = false;
    }

    void Dispatcher(){
        for(int i = 0; i < _pfds_size; ++i){
            // select返回来的fd_set:
            // 位置代表的是文件描述符，如果值为1代表就绪，反之不就绪！
            
            //fd不合法
            if(_pfds[i].fd == defaultfd) continue;
            
            // 合法但是不一定就绪
            // 就绪了↓
            // revents & POLLIN为真表示，读事件就绪！
            if(_pfds[i].revents & POLLIN){
                //问题是，就绪了也不太清楚，到底是链接就绪，还是普通套接字的就绪？

                // 链接就绪
                if(_pfds[i].fd == _listensock->GetFd()) Accepter();
                // 普通套接字读就绪
                else Recver(i);
            }
            //下面还可以针对于不同的fd_set做操作！这里不写了！
        }
    }

    // 链接管理器——链接管理 (把链接设置到内核)
    void Accepter(){
        InetAddr client;
        int sockfd = _listensock->AcceptOrDie(&client);
        
        // 在之前是一获取就直接进行read or recv 读取！
        // 但是，默认是阻塞的！ 我们这里是要等和IO操作分离的！
        // 所以，新获得的文件描述符，不一定能够马上读取 -> 交给select去接管
    
        // 如何接管？
        // 直接添加到辅助数组中！
        // 退出该函数的时候，会重新进行select等待，在select之前，会把辅助数组内的合法文件描述符导入到fd_set内！
        int pos = 0;  
        for(pos = 0; pos < _pfds_size; ++pos)
            if(_pfds[pos].fd == defaultfd) break;
        
        //但是出来的时候，pos可能是合法位置，也可能是_pfds_size位置
        if(pos == _pfds_size) {
            //说明当前容量满了，进行扩容！
            LOG(LogLevel::WARNING) << "select server full -> server realloc";
            _pfds_size *= 2;
            struct pollfd* tmp = new struct pollfd[_pfds_size];
            Init_pfds(_pfds_size, tmp);

            std::cout << 1 << std::endl;

            //拷贝旧数据
            for(int i = 0; i < pos; ++i) tmp[i] = _pfds[i];
            std::swap(tmp, _pfds);
            delete tmp;
        }

        // 把新的链接交给poll接管，然后关注读事件！
        _pfds[pos].fd = sockfd;
        _pfds[pos].events |= POLLIN;
    }

    // IO处理器——这里只有读
    // 这里只需要把辅助数组中，文件描述符 存储位置下标拿过来即可
    void Recver(int index){
        char buffer[1024] = {0};
        int n = recv(_pfds[index].fd, buffer, sizeof(buffer) - 1, 0);
        if(n < 0){
            // 读取出错了！
            LOG(LogLevel::ERROR) << "read error";
            close(_pfds[index].fd);
           _pfds[index].fd = defaultfd;
           _pfds[index].events = 0;
           _pfds[index].revents = 0;
        }
        else if(n == 0){
            // 客户端退出
            LOG(LogLevel::ERROR) << "client quit";
            close(_pfds[index].fd);
            _pfds[index].fd = defaultfd;
           _pfds[index].events = 0;
           _pfds[index].revents = 0;
        }
        else{
            // 正常读取
            buffer[n] = 0;
            // 这里是echo server 直接打印即可
            LOG(LogLevel::INFO) << "client says@ " << buffer;
        }
    }

    void Stop(){_is_running = false;}

    void PrintFdArray(){
        for(int i = 0; i < _pfds_size; ++i){
            if(_pfds[i].fd == -1) continue;
            else std::cout << _pfds[i].fd << " ";
        }
        std::cout << std::endl;
    }

private:
    uint16_t _port;
    bool _is_running;
    std::unique_ptr<Socket> _listensock;

    struct pollfd* _pfds;
    size_t _pfds_size;
};